package storeService

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"github.com/gomodule/redigo/redis"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"strconv"
	"time"
)

type StoreService struct {
	opts  *Options
	Mysql *sql.DB
	Mongo *mongo.Client
	Redis redis.Conn
}

func NewStoreService(typ StoreType, opts ...Option) (*StoreService, error) {
	s := new(StoreService)
	opts = append(opts, StoreTyped(typ))
	s.opts = newOptions(opts...)
	err := s.initialize()
	if err != nil {
		return nil, err
	}
	return s, nil
}

func (s *StoreService) initialize() error {
	switch s.opts.Type {
	case StoreMysql:
		return s.initMysql()
	case StoreMongodb:
		return s.initMongo()
	case storeRedis:
		return s.initRedis()
	}
	return errors.New("not support type")
}

func (s *StoreService) initMysql() error {
	args := fmt.Sprintf("charset=%s&loc=Local", s.opts.Charset)
	dsn := fmt.Sprintf(
		"%s:%s@tcp(%s:%d)/%s?%s",
		s.opts.User,
		s.opts.Passwd,
		s.opts.Host,
		s.opts.Port,
		s.opts.Db,
		args,
	)
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return err
	}
	db.SetConnMaxIdleTime(s.opts.ConnMaxIdleTime)
	db.SetConnMaxLifetime(s.opts.ConnMaxLifetime)
	db.SetMaxIdleConns(s.opts.MaxIdleConns)
	db.SetMaxOpenConns(s.opts.MaxOpenConns)
	s.Mysql = db
	return nil
}

func (s *StoreService) initMongo() error {
	var dsn string
	if len(s.opts.User) > 0 {
		dsn = fmt.Sprintf(
			"mongodb://%s:%s@%s:%d/%s",
			s.opts.User,
			s.opts.Passwd,
			s.opts.Host,
			s.opts.Port,
			s.opts.Db,
		)
	} else {
		dsn = fmt.Sprintf(
			"mongodb://%s:%d",
			s.opts.Host,
			s.opts.Port,
		)
	}

	client, err := mongo.NewClient(options.Client().ApplyURI(dsn))
	if err != nil {
		return err
	}
	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
	defer cancel()
	err = client.Connect(ctx)
	if err != nil {
		return err
	}
	s.Mongo = client
	return nil
}

func (s *StoreService) initRedis() error {
	var db int
	var err error
	opts := make([]redis.DialOption, 0)
	if len(s.opts.User) > 0 {
		opts = append(opts, redis.DialUsername(s.opts.User))
		opts = append(opts, redis.DialPassword(s.opts.Passwd))
	}
	if len(s.opts.Db) > 0 {
		db, err = strconv.Atoi(s.opts.Db)
		if err != nil {
			return err
		}
		opts = append(opts, redis.DialDatabase(db))
	}
	c, err := redis.Dial("tcp",
		fmt.Sprintf("%s:%d", s.opts.Host, s.opts.Port),
		opts...,
	)
	if err != nil {
		return err
	}
	defer c.Close()
	s.Redis = c
	return nil
}
